/*
 * HackSQL
 * Copyright (C) 2008-2009
 *
 * Authors: Higor Eurípedes Pimentel Fernandes de Araújo
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 * 
 * Original Code by: Christopher Clark (C) 2002 * <firstname.lastname@cl.cam.ac.uk>
 * 
 * Original Header:
 */
/*
 * Copyright (c) 2002, Christopher Clark
 * All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 
 * * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 * 
 * * Redistributions in binary form must reproduce the above copyright
 * notice, this list of conditions and the following disclaimer in the
 * documentation and/or other materials provided with the distribution.
 * 
 * * Neither the name of the original author; nor the names of any contributors
 * may be used to endorse or promote products derived from this software
 * without specific prior written permission.
 * 
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER
 * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <math.h>
#include <hacksql/hs_hashtable.h>

#define indexFor(tablelength, hashvalue) (hashvalue % tablelength)

static const unsigned int primes[] = {
	53, 97, 193, 389, 769, 1543, 3079, 6151, 12289, 24593, 49157, 98317,
	196613, 393241, 786433, 1572869, 3145739, 6291469, 12582917, 25165843,
	50331653, 100663319, 201326611, 402653189, 805306457, 1610612741
};

const unsigned int prime_table_length = sizeof(primes) / sizeof(primes[0]);
const float max_load_factor = 0.65;

// this one is private
static int _hs_hashtable_key_compare (void *k1, void *k2)
{
	return (strcmp(k1,k2) == 0);
}

hs_hashtable *hs_hashtable_create ()
{
	return hs_hashtable_create_ex(0);
}

hs_hashtable *hs_hashtable_create_ex (unsigned int minsize)
{
	hs_hashtable *h;
	unsigned int pindex, size = primes[0];
	
	/* Check requested hashtable isn't too large */
	if (minsize > (1u << 30)) {
		return NULL;
	}
	
	/* Enforce size as prime */
	for (pindex = 0; pindex < prime_table_length; pindex++) {
		if (primes[pindex] > minsize) {
			size = primes[pindex];
			break;
		}
	}
	
	h = (hs_hashtable *) malloc(sizeof(hs_hashtable));
	
	if (NULL == h) {
		return NULL;
	}
	
	h->table = (hs_hashtable_entry **) malloc(sizeof(hs_hashtable_entry *) * size);
	
	if (NULL == h->table) {
		free(h);
		return NULL;
	}
	
	memset(h->table, 0, size * sizeof(hs_hashtable_entry *));
	h->tablelength = size;
	h->primeindex = pindex;
	h->entrycount = 0;
	h->hashfn = hs_compute_hash;
	h->eqfn = _hs_hashtable_key_compare;
	h->loadlimit = (unsigned int) ceil(size * max_load_factor);
	
	return h;
}


unsigned int hs_hashtable_hash (hs_hashtable * h, void *k)
{
	/* Aim to protect against poor hash functions by adding logic here
	 * - logic taken from java 1.4 hashtable source */
	unsigned int i = h->hashfn(k);
	
	i += ~(i << 9);
	i ^= ((i >> 14) | (i << 18));	/* >>> */
	i += (i << 4);
	i ^= ((i >> 10) | (i << 22));	/* >>> */
	
	return i;
}


int hs_hashtable_expand (hs_hashtable * h)
{
	/* Double the size of the table to accomodate more entries */
	hs_hashtable_entry **newtable;
	hs_hashtable_entry *e;
	hs_hashtable_entry **pE;
	unsigned int newsize, i, index;
	
	/* Check we're not hitting max capacity */
	if (h->primeindex == (prime_table_length - 1)) {
		return 0;
	}
		
	newsize = primes[++(h->primeindex)];
	newtable = (hs_hashtable_entry **) malloc(sizeof(hs_hashtable_entry *) * newsize);
	
	if (NULL != newtable) {
		memset(newtable, 0, newsize * sizeof(hs_hashtable_entry *));
		/* This algorithm is not 'stable'. ie. it reverses the list
		 * when it transfers entries between the tables */
		for (i = 0; i < h->tablelength; i++) {
			while (NULL != (e = h->table[i])) {
				h->table[i] = e->next;
				index = indexFor(newsize, e->h);
				e->next = newtable[index];
				newtable[index] = e;
			}
		}
		
		free(h->table);
		h->table = newtable;
	}
	/* Plan B: realloc instead */
	else {
		newtable = (hs_hashtable_entry **) realloc(h->table, newsize * sizeof(hs_hashtable_entry *));
		
		if (NULL == newtable) {
			(h->primeindex)--;
			return 0;
		}
		
		h->table = newtable;
		memset(newtable[h->tablelength], 0, newsize - h->tablelength);
		
		for (i = 0; i < h->tablelength; i++) {
			for (pE = &(newtable[i]), e = *pE; e != NULL; e = *pE) {
				index = indexFor(newsize, e->h);
				
				if (index == i) {
					pE = &(e->next);
				}
				else {
					*pE = e->next;
					e->next = newtable[index];
					newtable[index] = e;
				}
			}
		}
	}
	
	h->tablelength = newsize;
	h->loadlimit = (unsigned int) ceil(newsize * max_load_factor);
	
	return -1;
}


unsigned int hs_hashtable_count (hs_hashtable * h)
{
	return h->entrycount;
}


int hs_hashtable_insert(hs_hashtable * h, void *k, void *v)
{
	/* This method allows duplicate keys - but they shouldn't be used */
	unsigned int index;
	hs_hashtable_entry *e;
	
	if (++(h->entrycount) > h->loadlimit) {
		/* Ignore the return value. If expand fails, we should
		 * still try cramming just this value into the existing table
		 * -- we may not have memory for a larger table, but one more
		 * element may be ok. Next time we insert, we'll try expanding again.*/
		hs_hashtable_expand(h);
	}
	
	e = (hs_hashtable_entry *) malloc(sizeof(hs_hashtable_entry));
	
	if (NULL == e) {
		--(h->entrycount);
		return 0;
	}
	
	e->h = hs_hashtable_hash(h, k);
	index = indexFor(h->tablelength, e->h);
	e->k = k;
	e->v = v;
	e->next = h->table[index];
	h->table[index] = e;
	
	return -1;
}


/* returns value associated with key */
void * hs_hashtable_search (hs_hashtable * h, void *k)
{
	hs_hashtable_entry *e;
	unsigned int hashvalue, index;
	
	hashvalue = hs_hashtable_hash(h, k);
	index = indexFor(h->tablelength, hashvalue);
	e = h->table[index];
	
	while (NULL != e) {
		/* Check hash value to short circuit heavier comparison */
		if ((hashvalue == e->h) && (h->eqfn(k, e->k))) {
			return e->v;
		}
		
		e = e->next;
	}
	
	return NULL;
}

/* returns value associated with key */
void * hs_hashtable_remove(hs_hashtable * h, void *k)
{
	/* TODO: consider compacting the table when the load factor drops enough,
	 *       or provide a 'compact' method. */

	hs_hashtable_entry *e;
	hs_hashtable_entry **pE;
	void *v;
	unsigned int hashvalue, index;

	hashvalue = hs_hashtable_hash(h, k);
	index = indexFor(h->tablelength, hs_hashtable_hash(h, k));
	pE = &(h->table[index]);
	e = *pE;
	
	while (NULL != e) {
		/* Check hash value to short circuit heavier comparison */
		if ((hashvalue == e->h) && (h->eqfn(k, e->k))) {
			*pE = e->next;
			h->entrycount--;
			v = e->v;
			free(e->k);
			free(e);
			return v;
		}
		
		pE = &(e->next);
		e = e->next;
	}
	
	return NULL;
}


/* destroy */
void hs_hashtable_destroy(hs_hashtable * h, int free_values)
{
	unsigned int i;
	hs_hashtable_entry *e, *f;
	hs_hashtable_entry **table = h->table;
	
	if (free_values) {
		for (i = 0; i < h->tablelength; i++) {
			e = table[i];
			
			while (NULL != e) {
				f = e;
				e = e->next;
				//free(f->k);
				free(f->v);
				free(f);
			}
		}
	}
	else {
		for (i = 0; i < h->tablelength; i++) {
			e = table[i];
			while (NULL != e) {
				f = e;
				e = e->next;
				//printf("%i\n", f->k == NULL);
				//free(f->k);
				free(f);
			}
		}
	}
	
	free(h->table);
	free(h);
}

unsigned long hs_compute_hash(void *key)
{
	unsigned long hash = 5381;
	int c;

	while (c = *((int*)key++)) {
		hash = ((hash << 5) + hash) + c;	/* hash * 33 + c */
	}

	return hash;
}

void hs_hashtable_apply(hs_hashtable *h, hs_hashtable_apply_func func)
{
	int i, result;

	if (!h || !func) {
		return;
	}

	for (i = 0; i < h->tablelength; i++) {
		if (!h->table[i]) {
			continue;
		}

		result = func(h->table[i]);
		if (result < 0) {
			break;
		}
	}
}


/*int my_func (hs_hashtable_entry *e) {
	if (!e) return -1;
	printf("hs[%s] = %s\n", (char*)e->k, (char*)e->v);
	return 0;
}

int main () {
	hs_hashtable *hs = hs_hashtable_create();
	hs_hashtable_insert(hs, "key1", "val1");
	hs_hashtable_insert(hs, "key2", "val2");
	hs_hashtable_insert(hs, "key3", "val3");
	hs_hashtable_apply(hs, my_func);
	hs_hashtable_destroy(hs, 0);
	return 0;
}
*/

